package com.google.android.apps.skipbot;

import android.os.AsyncTask.Status;

/**
 * High level abstract class for the robot that provides user understandable
 * controls.
 */
public abstract class Robot implements RobotController, ServoProvider {
  protected final RobotController robotController;
  protected final RobotConfig robotConfig;
  protected final Servo[] servos;
  protected RobotMovementSequenceRunner sequenceRunner;

  public Robot(RobotController robotController, RobotConfig robotConfig) {
    this.robotController = robotController;
    this.robotConfig = robotConfig;
    this.servos = Servo.createServoArray(robotController, robotConfig);
  }

  /**
   * Moves the robot forward for the specified number of seconds.
   */
  public abstract void moveForward();

  /**
   * Moves the robot into a standing position from an unknown position.
   */
  public abstract void stand();

  /**
   * Moves the robot into a sitting position.
   */
  public abstract void sit();

  /**
   * Gets the angle that should be used when calibrating the robot for each servo.
   * @param servoIndex zero based index of the servo
   * @return angle to be used when calibrating the robot
   */
  public abstract int getCalibrationAngle(int servoIndex);

  /**
   * Stops the robot.
   */
  public void stop() {
    cancelSequenceRunner();
  }

  /**
   * Attaches all of the servos in the robot config to the appropriate pins.
   */
  public void attachAllServos() {
    for (int i = 0; i < robotConfig.getNumServos(); ++i) {
      attachServo(i);
    }
  }

  /**
   * Attaches a single servo to the pin specified in the robot config.
   *
   * @param servoIndex zero based index of the servo
   */
  public void attachServo(int servoIndex) {
    getServo(servoIndex).attach();
  }

  /**
   * Attaches a single servo to the specified pin effectively overriding the
   * robot config.
   *
   * @param servoIndex zero based index of the servo
   */
  @Override
  public void attachServoToPin(int servoIndex, int servoPin) {
    Servo newServo =
        new Servo(robotController, servoIndex, servoPin, servos[servoIndex].getCalibrationShift(),
            servos[servoIndex].getFlipped());
    servos[servoIndex] = newServo;
    newServo.attach();
  }

  /**
   * Detaches all of the servos for the robot so they are no longer receiving
   * signals.
   */
  public void detachAllServos() {
    for (int i = 0; i < robotConfig.getNumServos(); ++i) {
      detachServo(i);
    }
  }

  /**
   * Detaches a single servo.
   *
   * @param servoIndex zero based index of the servo
   */
  @Override
  public void detachServo(int servoIndex) {
    getServo(servoIndex).detach();
  }

  /**
   * Executes a number or movement sequences on a background thread, canceling
   * any currently running sequences.
   *
   * @param movementSequences the movement sequences to be executed
   */
  protected void executeMovementSequences(RobotMovementSequence... movementSequences) {
    cancelSequenceRunner();
    this.sequenceRunner =
        (RobotMovementSequenceRunner) new RobotMovementSequenceRunner().execute(movementSequences);
  }

  /**
   * Cancels the sequence runner if it is running.
   */
  protected void cancelSequenceRunner() {
    if (sequenceRunner != null) {
      if (sequenceRunner.getStatus() != Status.FINISHED) {
        sequenceRunner.cancel(true);
      }
    }
  }

  /**
   * Moves the given servo to the given angle.
   *
   * @param servoIndex index of the servo to move
   * @param angle angle to move the servo
   */
  @Override
  public void moveServo(int servoIndex, int angle) {
    getServo(servoIndex).moveServo(angle);
  }

  /**
   * Moves each servo to the given angle in degrees where the position in the
   * array is the servo index.
   *
   * @param angles an array of angles in degrees
   */
  @Override
  public void moveAllServos(int[] angles) {
    int[] sendAngles = new int[angles.length];
    // Make sure the servo objects stay in sync.
    for (int i = 0; i < angles.length; ++i) {
      Servo servo = getServo(i);
      servo.setCurrentAngle(angles[i]);
      sendAngles[i] = servo.getSendAngle(angles[i]);
    }
    this.robotController.moveAllServos(sendAngles);
  }

  /**
   * Sends the current angle of each servo to the robot.
   */
  public void refreshAllServos() {
    int[] sendAngles = new int[getNumServos()];
    for (int i = 0; i < sendAngles.length; ++i) {
      Servo servo = getServo(i);
      sendAngles[i] = servo.getSendAngle(servo.getCurrentAngle());
    }
    this.robotController.moveAllServos(sendAngles);
  }

  /**
   * Gets the specified servo.
   *
   * @param servoIndex logical index of the servo
   * @return Servo object for the given logical servo index
   */
  @Override
  public Servo getServo(int servoIndex) {
    assert (servoIndex < servos.length && servoIndex >= 0);
    return servos[servoIndex];
  }

  @Override
  public int getNumServos() {
    return servos.length;
  }
}
